package com.thoughtorigin.flux {
import com.thoughtorigin.util.ClassCreator;

import flash.utils.Dictionary;
import flash.utils.getDefinitionByName;

public class XMLFlux implements Flux {

    private var _propertyDictionary: Dictionary;
    private var _xmlProperties: XML;

    public function XMLFlux(xml: XML) {
        _propertyDictionary = new Dictionary();
        _xmlProperties = xml;
    }

    public function getProperty(name:String):Object {
        if(_propertyDictionary[name]) {
            if(!PropertyItem(_propertyDictionary[name]).createNew) {
                return PropertyItem(_propertyDictionary[name]).object;
            }
        }
        var node: XML = getNode(name);
        var obj: Object = createInstanceByName(name, getConstructorArgs(node));
        if(node.@createNew == "true") {
            PropertyItem(_propertyDictionary[name]).createNew = true;            
        }
        PropertyItem(_propertyDictionary[name]).xmlNode = node;                    
        assignProperties(obj, node);
        executeFunctions(obj, node);
        return obj;
    }

    public function assignProperties(obj: Object, xmlProperties: XML): void {
        for each(var prop: XML in xmlProperties.properties.property) {
            //todo: use isBlank when it exists
            if(prop.@value.toString() != "") {
                obj[prop.@name] = prop.@value;
            } else if(prop.@ref.toString() != "") {
                obj[prop.@name] = getProperty(prop.@ref);
            }
        }
    }

    public function executeFunctions(obj: Object, xmlFunctions: XML): void {
        for each(var funcXML: XML in xmlFunctions.functions.exec) {
            var func: Function = obj[funcXML.@name];
            var args: Array = getArgs(funcXML);
            func.apply(this, args);
        }
    }

    private function getArgs(xmlNode: XML): Array {
        var retVal: Array = [];
        for each(var argsXML: XML in xmlNode.arg) {
            if(argsXML.@value.toString() != "") {
                retVal.push(argsXML.@value);                
            } else if(argsXML.@ref.toString() != "") {
                retVal.push(getProperty(argsXML.@ref));
            }
        }
        return retVal;
    }

    public function createNew(name:String):Object {
        var node: XML = getNode(name);
        var retVal: Object = createInstanceByName(name, getConstructorArgs(node));
        assignProperties(retVal, node);
        executeFunctions(retVal, node);
        return retVal;
    }

    private function createInstanceByName(name: String, args: Array): Object {
        var clazz: Class = getClass(name);
        if(clazz) {
            var retVal: Object = createInstance(clazz, args);
            var propertyItem: PropertyItem = new PropertyItem();
            propertyItem.object = retVal;
            if(!_propertyDictionary[name]) {
                _propertyDictionary[name] = propertyItem;                
            }
            return retVal;
        }
        throw new PropertyNotFoundException(name + " was not found in IOC.");
    }

    private function getConstructorArgs(xmlNode: XML): Array {
        var retVal: Array = [];

        if(!xmlNode || !xmlNode.constructor) return retVal;

        for each(var constArg: XML in xmlNode.constructor.arg) {
            //todo: use isBlank when it exists
            if(constArg.@value.toString() != "") {
                retVal.push(constArg.@value);
            } else if(constArg.@ref.toString() != "") {
                retVal.push(getProperty(constArg.@ref));
            }
        }
        return retVal;
    }

    public function getClass(name:String):Class {
        var node: XML = getNode(name);
        if(!node) {
            return null;
        }
        var className: String = node.@object;
        try {
            return Class(getDefinitionByName(className));
        } catch(e: ReferenceError) {
            throw new ClassDefinitionNotFoundException(className + " was not found in applicationDomain.")
        }
        return null;
    }

    private function getNode(name: String): XML {
        for each(var def: XML in _xmlProperties.Def) {
            if(def.@name == name) {
                return def;
            }
        }
        return null;
    }

    /**
     * creates objects up to 9 args
     *
     * @param args
     * @return
     */
    protected function createInstance(clazz: Class, args: Array): Object {
        return ClassCreator.create(clazz, args);
    }
}

}

class PropertyItem {
    public var object: Object;
    public var createNew: Boolean = false;
    public var xmlNode: XML;
}
